package com.gitee.wlpxxx;

import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.charset.StandardCharsets;

public class ByteArray {
    private ByteBuffer innerBuffer = null;
    public boolean error = false;
    public ByteArray() {
        clearAll();
        littleEndian();
    }
    public ByteArray(byte[] data) {
        clearAll();
        writeBytes(data);
        position(0);
    }
    public ByteArray(byte[] data, boolean isLittleEndian) {
        clearAll();
        writeBytes(data);
        position(0);
        if (isLittleEndian) {
            littleEndian();
        } else {
            bigEndian();
        }
    }
    public ByteArray(boolean isLittleEndian) {
        clearAll();
        if (isLittleEndian) {
            littleEndian();
        } else {
            bigEndian();
        }
    }
    private void innerReset(int size) {
        if (size < 0) {
            error = true;
            return;
        } else {
            error = false;
        }
        if (innerBuffer == null) {
            innerBuffer = ByteBuffer.allocate(size);
        } else {
            byte[] data;
            ByteOrder order = innerBuffer.order();
            if (innerBuffer.capacity() < size) {
                data = innerBuffer.array();
                innerBuffer = ByteBuffer.allocate(size);
                innerBuffer.put(data);
            } else {
                data = new byte[size];
                innerBuffer.position(0);
                innerBuffer.get(data, 0, size);
                innerBuffer = ByteBuffer.wrap(data);
            }
            innerBuffer.order(order);
        }
        innerBuffer.position(0);
    }
    private int innerExtend(int size) {
        if (size < 1) {
            error = true;
            return position();
        } else {
            error = false;
        }
        int pos = position();
        int newLength = pos + size;
        if (innerBuffer.capacity() < newLength) {
            innerReset(newLength);
        }
        return position(pos);
    }
    public void clearAll() {
        innerReset(0);
    }
    public int length() {
        return innerBuffer.capacity();
    }
    public void length(int size) {
        innerReset(size);
    }
    public int position() {
        return innerBuffer.position();
    }
    public int position(int position) {
        if (position > length()) {
            error = true;
            position = length();
        } if(position < 0) {
            error = true;
            position = 0;
        }else {
            error = false;
        }
        innerBuffer.position(position);
        return position();
    }
    public int offset(int offset) {
        error = false;
        if (position() + offset > length()) {
            int position = position(length());
            error = true;
            return position;
        } else if (position() + offset < 0) {
            int position = position(0);
            error = true;
            return position;
        } else {
            return position(position() + offset);
        }
    }
    public int availableSize() {
        return innerBuffer.capacity() - innerBuffer.position();
    }
    public void bigEndian() {
        innerBuffer.order(ByteOrder.BIG_ENDIAN);
    }
    public void littleEndian() {
        innerBuffer.order(ByteOrder.LITTLE_ENDIAN);
    }
    public boolean isBigEndian() {
        return innerBuffer.order() == ByteOrder.BIG_ENDIAN;
    }
    public boolean isLittleEndian() {
        return innerBuffer.order() == ByteOrder.LITTLE_ENDIAN;
    }
    public byte[] bufferBytes() {
        return innerBuffer.array();
    }
    public int writeBool(boolean value) {
        return writeInt8((byte)(value ? 1 : 0));
    }
    public int writeInt8(byte value) {
        innerExtend(Byte.SIZE / Byte.SIZE);
        innerBuffer.put(value);
        return position();
    }
    public int writeInt8(int value) {
        return writeInt8((byte)value);
    }
    public int writeInt16(short value) {
        innerExtend(Short.SIZE / Byte.SIZE);
        innerBuffer.putShort(value);
        return position();
    }
    public int writeInt16(int value) {
        return writeInt16((short)value);
    }
    public int writeInt32(int value) {
        innerExtend(Integer.SIZE / Byte.SIZE);
        innerBuffer.putInt(value);
        return position();
    }
    public int writeInt64(long value) {
        innerExtend(Long.SIZE / Byte.SIZE);
        innerBuffer.putLong(value);
        return position();
    }
    public int writeInt64(int value) {
        return writeInt64((long)value);
    }
    public int writeFloat32(float value) {
        innerExtend(Float.SIZE / Byte.SIZE);
        innerBuffer.putFloat(value);
        return position();
    }
    public int writeFloat32(double value) {
        return writeFloat32((float)value);
    }
    public int writeFloat64(double value) {
        innerExtend(Double.SIZE / Byte.SIZE);
        innerBuffer.putDouble(value);
        return position();
    }
    public int writeFloat64(float value) {
        return writeFloat64((double) value);
    }
    public int writeBytes(byte[] value) {
        return writeBytes(value, value.length);
    }
    public int writeBytes(byte[] value, int size) {
        innerExtend(size);
        int pos = position();
        innerBuffer.put(value, 0, value.length < size ? value.length : size);
        return position(pos + size);
    }
    public int writeString(String value) {
        writeBytes(value.getBytes(StandardCharsets.UTF_8));
        return writeInt8((byte) 0);
    }
    public int writeString(String value, int size) {
        return writeBytes(value.getBytes(StandardCharsets.UTF_8), size);
    }
    public int writeUTFString(String value) {
        ByteArray buf = new ByteArray(isLittleEndian());
        buf.String(value);
        Short(buf.length() - 1);
        return Bytes(buf.bufferBytes(), buf.length() - 1);
    }
    public int writeUTFString(String value, int size) {
        ByteArray buf = new ByteArray(isLittleEndian());
        buf.String(value);
        Short(size);
        return Bytes(buf.bufferBytes(), size);
    }
    public boolean readBool() {
        return readInt8() == (byte)1;
    }
    public byte readInt8() {
        try {
            error = false;
            return innerBuffer.get();
        }catch (Exception e) {
            error = true;
            return 0;
        }
    }
    public short readInt16() {
        try {
            error = false;
            return innerBuffer.getShort();
        }catch (Exception e) {
            error = true;
            return 0;
        }
    }
    public int readInt32() {
        try {
            error = false;
            return innerBuffer.getInt();
        }catch (Exception e) {
            error = true;
            return 0;
        }
    }
    public long readInt64() {
        try {
            error = false;
            return innerBuffer.getLong();
        }catch (Exception e) {
            error = true;
            return 0;
        }
    }
    public float readFloat32() {
        try {
            error = false;
            return innerBuffer.getFloat();
        }catch (Exception e) {
            error = true;
            return 0;
        }
    }
    public double readFloat64() {
        try {
            error = false;
            return innerBuffer.getDouble();
        }catch (Exception e) {
            error = true;
            return 0;
        }
    }
    public byte[] readBytes(int size) {
        try {
            error = false;
            byte[] temp = new byte[size];
            if (innerBuffer.capacity() < innerBuffer.position() + size) {
                size = innerBuffer.capacity();
            }
            innerBuffer.get(temp, 0, size);
            return temp;
        }catch (Exception e) {
            error = true;
            return null;
        }
    }
    public byte[] readBytes() {
        return readBytes(availableSize());
    }
    public String readString(int size) {
        try {
            error = false;
            ByteArray buf = new ByteArray(readBytes(size));
            String res = buf.readString();
            error = buf.error;
            return res;
        }catch (Exception e) {
            error = true;
            return null;
        }
    }
    public String readString() {
        try {
            error = false;
            int len = 0;
            boolean eof = false;
            int pos = innerBuffer.position();
            for (int i = innerBuffer.position(); i< innerBuffer.capacity(); i++) {
                byte b = innerBuffer.get();
                if (b == (byte)0) {
                    eof = true;
                    break;
                }
                len++;
            }
            innerBuffer.position(pos);
            if (len == 0) {
                if (eof) {
                    readInt8();
                }
                return "";
            } else {
                byte[] temp = readBytes(len);
                if (eof) {
                    readInt8();
                }
                return new String(temp, StandardCharsets.UTF_8);
            }
        }catch (Exception e) {
            error = true;
            return null;
        }
    }
    public String readUTFString() {
        return String(Short() & 0xffff);
    }
    public boolean Bool() {
        return readBool();
    }
    public int Bool(boolean value) {
        return writeBool(value);
    }
    public byte Byte() {
        return readInt8();
    }
    public int Byte(byte value) {
        return writeInt8(value);
    }
    public int Byte(int value) {
        return writeInt8(value);
    }
    public short Short() {
        return readInt16();
    }
    public int Short(short value) {
        return writeInt16(value);
    }
    public int Short(int value) {
        return writeInt16(value);
    }
    public int Int() {
        return readInt32();
    }
    public int Int(int value) {
        return writeInt32(value);
    }
    public long Long() {
        return readInt64();
    }
    public int Long(int value) {
        return writeInt64(value);
    }
    public int Long(long value) {
        return writeInt64(value);
    }
    public float Float() {
        return readFloat32();
    }
    public int Float(float value) {
        return writeFloat32(value);
    }
    public int Float(double value) {
        return writeFloat32(value);
    }
    public double Double() {
        return readFloat64();
    }
    public int Double(float value) {
        return writeFloat64(value);
    }
    public int Double(double value) {
        return writeFloat64(value);
    }
    public String String() {
        return readString();
    }
    public String String(int size) {
        return readString(size);
    }
    public int String(String value) {
        return writeString(value);
    }
    public int String(String value, int size) {
        return writeString(value, size);
    }
    public String UTFString() {
        return readUTFString();
    }
    public int UTFString(String value) {
        return writeUTFString(value);
    }
    public int UTFString(String value, int size) {
        return writeUTFString(value, size);
    }
    public byte[] Bytes() {
        return readBytes(availableSize());
    }
    public byte[] Bytes(int size) {
        return readBytes(size);
    }
    public int Bytes(byte[] value) {
        return writeBytes(value);
    }
    public int Bytes(byte[] value, int size) {
        return writeBytes(value, size);
    }
}
